<!DOCTYPE html>
<title>Custom Elements: Constructor Tests</title>
<link rel="help" href="https://html.spec.whatwg.org/multipage/dom.html#elements-in-the-dom">
<meta name="author" title="Dominic Cooney" href="mailto:dominicc@chromium.org">
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="resources/custom-elements-helpers.js"></script>
<body>
<script>
'use strict';

setup({
  allow_uncaught_exception: true,
});

test_with_window((w) => {
  assert_throws_js(w.TypeError, () => {
    w.HTMLElement();
  }, 'calling the HTMLElement constructor should throw a TypeError');
}, 'HTMLElement constructor, invoke');

test_with_window((w) => {
  assert_throws_js(w.TypeError, () => {
    new w.HTMLElement();
  }, 'invoking the HTMLElement constructor with a construct call should ' +
     'throw a TypeError');
}, 'HTMLElement constructor, construct');

test_with_window((w) => {
  class X extends w.HTMLElement {}
  w.customElements.define('a-a', X);
  assert_throws_js(TypeError, () => {
    X();
  }, 'calling a custom element constructor should throw a TypeError');
}, 'Custom element constructor, invoke');

test_with_window((w) => {
  var num_constructor_invocations = 0;
  class C extends w.HTMLElement {
    constructor() {
      super();
      num_constructor_invocations++;
    }
  }
  w.customElements.define('a-a', C);
  let e = new C();
  assert_true(e instanceof w.HTMLElement,
              'the element should be an HTMLElement');
  assert_equals(e.localName,
                'a-a',
                'the element tag name should be "a-a"');
  assert_equals(e.namespaceURI,
                'http://www.w3.org/1999/xhtml',
                'the element should be in the HTML namespace');
  assert_equals(e.prefix,
                null,
                'the element name should not have a prefix');
  assert_equals(e.ownerDocument,
                w.document,
                'the element should be owned by the registry\'s associated ' +
                'document');
  assert_equals(num_constructor_invocations,
                1,
                'the constructor should have been invoked once');
  assert_true(e.matches(':defined'), 'custom element constructed with new should be "custom"');
}, 'Custom element constructor, construct');

test_with_window((w) => {
  class C extends w.HTMLElement { }
  class D extends C {}
  w.customElements.define('a-a', C); // Note: Not D.
  assert_throws_js(w.TypeError, () => {
    new D();
  }, 'constructing a custom element with a new.target with no registry ' +
     'entry should throw a TypeError');

  assert_throws_js(TypeError, () => {
    Reflect.construct(C, [], 42);
  }, 'constructing a custom element with a non-object new.target should ' +
     'throw a TypeError');
}, 'Custom element constructor, construct invalid new.target');

test_with_window((w) => {
  w.customElements.define('a-a', w.HTMLButtonElement);
  assert_reports_js(w, w.TypeError, () => {
    w.document.createElement('a-a');
  }, 'If NewTarget is equal to active function object, TypeError should be ' +
     'reported');
}, 'Custom element constructor, NewTarget is equal to active function object');

test_with_window((w) => {
  w.customElements.define('a-a', class extends w.HTMLButtonElement {} );
  assert_reports_js(w, w.TypeError, () => {
    w.document.createElement('a-a');
  }, 'If NewTarget is equal to active function object, TypeError should be ' +
     'reported');
}, 'Custom element constructor, active function object is not equal to HTMLElement');

test_with_window((w) => {
  let flag = true;
  class A extends w.HTMLElement {
    constructor() {
      if (flag) {
        flag = false;
        new A();
      }
      super();
    }
  }
  w.customElements.define('a-a', A);
  let e = new A();
  assert_true(e.matches(':defined'),
    'constructing a custom element with new should not throw InvalidStateError ' +
    'and should return a "custom" element');
}, 'Already constructed marker, construct with new');

test_with_window((w) => {
  let flag = true;
  class A extends w.HTMLElement {
    constructor() {
      if (flag) {
        flag = false;
        new A();
      }
      super();
    }
  }
  w.customElements.define('a-a', A);
  let e = w.document.createElement('a-a');
  assert_true(e.matches(':defined'),
    'constructing an autonomous custom element with create element should ' +
    'not throw InvalidStateError ' +
    'and should return a "custom" element');
}, 'Already constructed marker, create element');

test_with_window((w) => {
  let flag = true;
  class A extends w.HTMLElement {
    constructor() {
      if (flag) {
        flag = false;
        new A();
      }
      super();
    }
  }
  w.customElements.define('a-a', A);
  let d = w.document.createElement('div');
  assert_reports_js(window, w.TypeError, () => {
    d.innerHTML = '<a-a>';
  }, 'Creating an element that is already constructed marker should report ' +
     'InvalidStateError');
}, 'Already constructed marker, fragment parsing should set marker');

test_with_window((w) => {
  let errors = [];
  window.onerror = function (event, source, lineno, colno, error) {
    errors.push(error.name);
    return true;
  };
  let flag = true;
  let e = w.document.createElement('a-a');
  class A extends w.HTMLElement {
    constructor() {
      if (flag) {
        flag = false;
        new A();
      }
      super();
    }
  }
  w.customElements.define('a-a', A);
  w.document.body.appendChild(e);
  assert_array_equals(errors, ['TypeError'], 'Upgrading an element ' +
    'that is already constructed marker should throw TypeError');
}, 'Already constructed marker, upgrade element');
</script>
